بروتوكول TCP


deliver مسلمة transmit يرسل

في هذا الفصل سوف نتطرق الى بروتوكول TCP للإتصالات الموجهه connection-oriented مع مقابس السيل التي تؤدي إتصالات موثوقة ونقل متسلسل ذو أتجاهين ، ومن الممكن التعامل مع مقابس TCP كما تعاملنا سابقاً مع مقابض الملفات والأنابيب بمعنى بعد أن يفتح مقبس TCP يمكن إرسال البيانات من خلال ذلك المقبس عن طريق الكتابة الية print() أو syswrite() أو إستلام البيانات عن طريق القراءة <>, read(), sysread() . .


لإنشاء خادم نتبع الخطوات التالية:


ولانشاء تثبيث(Establishing)العميل فسيكون أكثر سهوله:


Simple Server

يشغل هنا خادم بسيط على جهاز واحد ويخدم برنامج واحد فقط للعميل في كل مرة اتصال من نفس أو من مختلف الأجهزه .

#!/usr/bin/perl -w
# Server1.pl : a simple server

use strict;
use Socket;

my $port = shift || 7890;
my $proto = getprotobyname('tcp');

socket(SERVER, PF_INET, SOCK_STREAM, $proto)
        or die "socket: $!";

setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1)
        or die "setsock: $!";

my $paddr = sockaddr_in($port, INADDR_ANY);

# bind to a port, then listen
bind(SERVER, $paddr)      or die "bind: $!";

listen(SERVER, SOMAXCONN) or die "listen: $!";
print "SERVER started on port $port\n";
# for each connection...
my $client_addr;

while ($client_addr = accept(CLIENT, SERVER)) {
    my ($client_port, $client_ip) =
                     sockaddr_in($client_addr);
    my $client_ipnum = inet_ntoa($client_ip);
    my $client_host =
             gethostbyaddr($client_ip, AF_INET);
    print "got a connection from: $client_host",
          " [$client_ipnum]\n";

   # send them a message, close connection
    print CLIENT "Hello from the server: ",
    close CLIENT;
}

الشرح :

شحن الوحدة strict لفحص الصيغة وشحن Socket لإستعمال الثوابت المتعلقة بالمقبس ، ثم الاعلان عن متغيرات ويخصص $port لرقم المنأفذ الأفتراضي 7890 عندما لايضع المستخدم المنفذ في محث الاوامر، بإختيار منأفذ للخادم ينصح باختيار منأفذ غير مخصص على جهازك يعني من فوق 1024 الى 65535 فقط للضمان وعدم التعارض لمنفذ موجود ( قم بمشاهده المنظور على RFC وهي مستندات للأعمال المرتبطة بالانترنت request for comments)..ثم إيجاد رقم بروتوكول TCP بواسطة getprotobyname() لكي يستخدم مع socket()

أنشاء المقبس بإستدعى socket() ولإنشاء المقبس تحتاج الى ثلاث خطوات:

use Socket;
my $proto = getprotobyname('tcp');
socket(SERVER, PF_INET, SOCK_STREAM, $proto)
        or die "socket: $!";

ينشأ الخادم مقبس يدفق فيه البيانات بشكل السيل في مجال الإنترنت عن طريق إستخدام بروتوكول TCP ، وسطاء الدالة :

Socket : المقبس SERVER وهو كمقبض الملف يمكن ان يقراء منه او يكتب إلية

domain : عائلة العنوان

type : نوع المقبس من نوع Stream ( خلاف بروتوكول UDP من النوع Datagram )

protocol : البروتوكول المستخدم



تمنح لنا setsockopt() لتغير خيارات المقبس (تقديم SO_ امامها تعني Socket Option) وفي هذة الحال ة وضع الخيار SO_REUSEADDR للمقبس لضمان ذلك المنأفذ لكي يكون قابل للاستعمال مره اخرى

setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1)
        or die "setsock: $!";

الوسطاء

Socket : المقبس SERVER المنشأ سابقاً

level : الباراميتر الثاني و هو بروتوكول مستقل ، وهكذا تقبض بواسطة طبقة المقبس نفسها (SOL_SOCKET)،البعض يحددها بواسطه TCP بروتوكول النقل (IPPROTO_TCP) ، والبعضها تقبض بواسطة IP بروتوكول الانترنت (IPPROTO_IP) .

optName : لديه عده ثوابت وكلاً منها لديها وظيفه معينة ، فالثابت SO_REUSEADDR يسمح للربط الى العنوان أو منأفذ الذي هوعلى قيد الاستعمال ، فهو يقوم بتمكين العديد من المقابس لتحدد الى نفس العنوان قبل استدعى الدالة bind ، وهذا الخيار يستدعي الدالة bind بنجاح بالرغم من وجود أي من المقابس التي قد ارتباطت سابقاً الى منأفذ الخادم ، فبدون هذا الخيار ستفشل الدالة bind بإعادة الربط بنفس العنوان حتى تصبح الاتصالات القديمة تاخذ timed out والتي قد تأخذ العملية عددة دقائق .

optValue : توضع في بعض الاحيان للإشارة الى الخيار الذي يجب ان يكون سواءاً ON أو OFF


تركيب عنوان المقبس socket address يجمع بين عنوان المضيف host والبروت لضغطهم معاً وتحوليهم للصيغة الثنائي من خلال sockaddr_in

$socket_addr = sockaddr_in($port,$address) 
($port,$address) = sockaddr_in($socket_addr) 

فعندما الدالة تستدعى في سياق scalar بإخذ رقم المنأفذ وعنوان IP الثنائي فسوف تضغطهم معاً الى عنوان المقبس ، وعندما نستدعيها في سياق القائمة فأن الدالة ستقوم بالعكس اي بترجمة عنوان المقبس الى البرورت وعنوان IP ، ثم عنوان IP يجب إن يمر بعد ذلك الى الدالة inet_ntoa للحصول على النصوص المقرواء طبعتاً

هكذا الدالة sockaddr_in تسمح أما بالضغط في سياق scalar كما هي في حالة الخادم السابق أو بواسطة فك الضغط في سياق القائمة ،ويمكن إستعمال هذة الدالة بشكل واضح وغير مشوش من خلال الدالة pack_sockaddr_in للضغط ، وأستعمال الدالة unpack_sockaddr_in لفك الضغط

my $paddr = sockaddr_in($port, INADDR_ANY);

البارامتر INADDR_ANY يأخذ احدا عناونين IP الافتراضي للخادم ويفعل بأي IP أوتوماتيكياً للخام ، فنظام التشغل يسمح لقبول الإتصالات على إي عنوان IP مضيف (سوا متضمن عنوان الـloopback وهو 127.0.0.1 ويعرف "localhost" ، أو إي عنوان أنترنت IP عام )

ونستطيع تحديد العميل المقبول بالضبط باستبدال INADDR_ANY عن طريق الدوال

$packed_address=inet_aton("192.168.1.1")
or 
$packed_address=gethostbyname("server.onsight.com")

ثم الدالة bind تربط المقبس بالمنأفذ، يوصل المقبس الى ذلك المنأفذ .

bind(SERVER, $paddr)      or die "bind: $!";

برنامج العميل يخبر نظام التشغيل بإختيار عنوان IP ورقم المنأفذ لكي يستعمل عند إستدعى الدالة connect ، بينما برنامج الخادم لابد وان يكون لدية عنوان معروف لكي يلتقون العملاء معة ، ولهذا السبب لابد من ربط المقبس مع عنوانة IP المحلى ورقم المنأفذ ، وتعرف هذة العملية بالـ binding .

ثم الدالة listen

listen(SERVER, SOMAXCONN) or die "listen: $!";

فالدالة listen تخبر النظام بأنك تريد إنتظار اتصالات قادمة ، ثاني بارامتر للدالة هو طول الاقصى للطابور maximum queue length وهو القيمة الاقصى للنظامك للموافقه على الاتصالات القادمة حيث النظام سيضع الأتصالات القادمة(العملاء) في الطابور queue في مبدأ FIFO الداخل أولاً خارج أولاً ، وطول الطابور يحدد عدد الإتصالات القادمة التي يمكن ان توضع في الطابور للإنتظار الموافقة .

يبداء الخادم بالإستماع على المنأفذ فعند محاوله شخص ما بالإتصال بالخادم فلابد من مؤافقة الخادم على الاتصال بواسطة الدالة accept في هذة اللحظة تستدعى الدالة accept عادتاً في تكرار لكي تستدعى في كل وقت وتعيد مقبس إتصال جديد مربوط الى الـنظير peer كما هي مشاهد في الصورة حيث ان المقبس المستمع لايتأثر بهذة العملية وعندما الاتصال يثبت فأننا نستطيع من إرسال وإستلام البيانات




عندما accept تستلم الاتصال القادم ،وتعيد مقبس جديد متصل إلى العميل


$client_addr = accept(CLIENT, SERVER)

وهذا سنشئ مقبض الملف CLIENT وهو المقبس الجديد ويستخدم للاتصال مع العميل ، فالقراءة من المقبس فأنة يقراء خرج العميل وتطبعه الى المقبس وترسل البيانات الى العميل .

والقيمة العائده من الدالة accept هو عنوان الانترنت ip العميل على صيغة حزمة، اي ان المتغير client_addr سيكون فيه عنوان المنأفذ العميل مضغوط ، ويمكن فك الضغط

my ($client_port, $client_ip) =sockaddr_in($client_addr);
my $client_ipnum = inet_ntoa($client_ip);

فالدالة sockaddr_in تأخذ تلك الحزمة وتعيد رقم المنأفذ العميل وIP العددي في صيغة حزمة .

اما الدالة inet_ntoa تحول حزمة IP العددية الى نصوص باستعمال

inet_ntoa (numeric to ASCII).

ويمكن انجاز الشيء نفسة لتحويل حزمه العنوان الى host nameبإستخدام الدالة gethostbyaddr


للقراءة من مقبض الملف او من المقبس في البيرل نستخدم الرموز كالملف (<FH>) وللكتابة الية نستعمل الدالة print

print CLIENT "Hello from the server: "

بفرض بأن اسم الجهاز هو server.onsight.com. ونقوم بتشغيل الخادم بالامر :


Output

% server1.pl
SERVER started on port 7890



الخادم الان يستمع في المنأفذ 7890 على server.onsight.com وينتظر العملاء للاتصال




Simple Client

#! /usr/bin/perl -w
# client1.pl - a simple client

use strict;
use Socket;

my $host = shift || 'server.onsight.com';
my $port = shift || 7890;

my $proto = getprotobyname('tcp');

# get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);

# create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
                        or die "socket: $!";
connect(SOCKET, $paddr) or die "connect: $!";
while (<SOCKET>) {
        print;
}
close SOCKET or die "close: $!";

في مثال العميل فأن اول وسيط للسطر الاوامر هو hostname الذي يجب ان يتصل معه والافتراضي هو server.onsight.com ، وثاني وسيط هو رقم المنأفذ والافتراضي هو 7890.

فالإسم المضيف hostname و رقم المنأفذ يستعملان للتوليد عنوان المنأفذ باستعمال

inet_aton (ASCII to numeric)
my $paddr = sockaddr_in($port, $iaddr);

ثم المقبس ينشئ بستعمال socket ، ثم يقوم العميل بالاتصال ويوصل المقبس الى عنوان المنأفذ بالاستعمال connect.

وفي التكرار while تقوم بقراء البيانات التي يرسلها الخادم الى العميل حتي يصل الى نهاية الملف ) end-of-file المتغير الخاص $_ افتراضي )، ويطبع هذا المدخل الى STDOUT، ثم المقبس يغلق .

نفترض كل العميل يبداء على الجهاز المسمى client.avue.com ، بالرغم من انهم يمكنهم ان ينفذو من اي جهاز في الشبكه . لتنفيذ العميل :


Output

% client1.pl server.onsight.com
Hello from the server: Tue Oct 27 09:48:40 2004



و خرج الخادم:


Output

got a connection from: client.avue.com [192.168.1.2]





A TCP Echo Client

بتطوير برنامج العميل المشاهدين في الفصل السابق ، يهتم العملاء مبدئياً بإنشاء المقبس وبداء اتصالهم الى خدمة الند البعيد بالعنوان المستقبل المطلوب وربط المقبس الية ثم انجاز مختلف I/O على المقبس ويغلق المقبس عند الإنتهاء كما رينا أستمرار أزعاج الاطفال في العمليات والانابيب في الفصول السابق حتى نغلق اتصال المقبس بإستدعاء الدالة close() ، فمثلاً هنا كتابة عميل بسيط لخدمة TCP "echo" المشغلة كخدمة افتراضية في العديد من مضيفين اليونكس echo server التي تنتظر للإتصالات القادمة وقبول الاتصال ومن ثم محاكة كل بايت يستلم

#!/usr/bin/perl
# file: tcp_echo_cli1.pl
# A TCP Echo Client

# usage: tcp_echo_cli1.pl [host] [port]

use strict;
use Socket;
use IO::Handle;
my ($bytes_out,$bytes_in) = (0,0);

my $host = shift || 'localhost';
my $port = shift || getservbyname('echo','tcp');

my $protocol = getprotobyname('tcp');
$host = inet_aton($host) or die "$host: unknown host";

socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die "socket() failed: $!";
my $dest_addr = sockaddr_in($port,$host);
connect(SOCK,$dest_addr) or die "connect() failed: $!";

SOCK->autoflush(1);

while (my $msg_out = <>) {
    print SOCK $msg_out;
    my $msg_in = <SOCK>;
    print $msg_in;

    $bytes_out += length($msg_out);
    $bytes_in  += length($msg_in);
}

close SOCK;
print STDERR "bytes_sent = $bytes_out, bytes_received = $bytes_in\n";

الشرح :

شحن الوحدة IO::Handle لإستعمال الطريقة autoflush() وشحن Socket لإستعمال الثوابت المتعلقة بالمقبس ، ثم الاعلان عن متغيرات عامة لمعرفة عدد البايت المرسلة والمستلمة .

ثم كما في السابق للقراءة المضيف وإيجاد رقم المنفذ لخدمة echo بأستخدم getservbyname() ، ثم إيجاد رقم بروتوكول TCP بواسطة getprotobyname() لكي يستخدم مع socket() ، ثم إستعمال الدالة inet_aton() لإرجاع المضيف المدخل الى عنوان IP مضغوط لكي يستخدم مع sockaddr_in() .

ثم استدعاء socket() لإنشاء المقبس المسمى SOCK بتحديد عائلة عنوان الانترنت AF_INET واستناد المقبس بنوع السيل SOCK_STREAM ورقم بروتوكول TCP المعد اعلى .

لإنشاء عنوان المستقبل نستعمل sockaddr_in() لتنشأ عنوان مضغوط يحوي عنوان IP ورقم المنفذ المستقبل ، وبهذا العنوان المضغوط نتصل بالجهاز البعيد عن طريق connect() .

يفتح نمط autoflush للمقبس فنحن نرغب في مرور البيانات المكتوبة الى المقبس لتجرف flushed مباشرتاً بدلاً من بقاءها في ال buffer المحلي ، ثم في التكرار يقراء في كل مرة سطر مدخل من النصوص من الدخل القياسي ، ويكتب المدخل الى مقبض المقبس SOCK لكي يرسل الى المضيف البعيد ،ثم قراءة سطر مردود من الخادم ، ثم طباعتة في الخرج القياسي للسكربت .

يتم حساب عدد البايت المرسلة والمستلامة في كل مرة خلال التكرار وحتي نصل الى EOF في الدخل القياسي ، ثم أغلاق المقبس وطباعة الى الخطأ القياسي عدد البايت المرسلة والمستلام


Output

% tcp_echo_cli1.pl www.modperl.com
How now brown cow?
How now brown cow?
There's an echo in here.
There's an echo in here.
Yo-de-lay-ee-oo!
Yo-de-lay-ee-oo!
^D
bytes_sent = 61, bytes_received = 61



باصدار ^D لمدخل السطر الاخير لضغظ مفتاح لنهاية المدخل end-of-input (على أنظمة الونيدوز يكون ^Z )


دوال المقبس المتعلقة بالإتصالات الخارجة :

سنرى بعض الدوال المتعلقة بإنشاء المقبس وتثبت إتصالات TCP الخارجة في اكثر تفصيل


$boolean = socket (SOCKET,$domain,$type,$protocol)

الدالة لإنشاء مقبس جديد وربطة بمقبض المقبس حيث نستطع التعامل مع المقبس الجديد بواسطة هذا المقبض ، وسطاء هذة الدالة أسم مقبض المقبس ،والمجال ، والنوع ،والبروتوكول ، تعيد الدالة القيمة true في النجاح ، و في الخطأ تعيد undef وتترك رسائلة الخطأ في المتغير $! .

فكلاًَ من المجال والنوع والبروتوكول جميعهم أعداد صحيحة integers صغيرة ، الاثنين الاولين هم ثوابت معرفة في وحـدة Socket لكن قيمة البروتوكول يجب ان تحسب في وقت التشغيل بالإستدعاء getprotobyname() ، والطريقة العامة لإنشاء مقابس TCP :

socket(SOCK,AF_INET,SOCK_STREAM, scalar getprotobyname("tcp"))

وهنا نجبر getprotobyname() الى سياق scalar لكي تعيد في هذا السياق لرقم البروتوكول فقط .

$boolean = connect (SOCK,$dest_addr)

تقوم هذة الدالة بمحاول إتصال او ربط مقبس الإتصال الموجهه إلى عنوان المستقبل المقصود ، وتستدعى هذة الدالة بعد إنشاء المقبس مع socket() ، وسطاء الدالة هو المقبس SOCK الذي منشاء من قبل و عنوان المستقبل المضغوط $dest_addr منشأ من قبل sockaddr_in() أو ماتكافئها ، وبشكل آلي يختار النظام المنفذ العابر ephemeral port ليستخدم لعنوان المقبس المحلي .

تعيد الدالة القيمة true في النجاح ، وفي الخطأ تعيد false ويوضع في المتغير $! رسائلة الخطأ لتوضيح المشكلة ، ومن غير شرعي illegal لمقبس الإتصال الموجهه من استدعاء connect() أكثر من مرة ، وبإستدعاء الدالة مرة اخرى يولد في EISCONN الخطأ ("Transport endpoint is already connected")

$boolean = close (SOCK)

تعمل مع المقابس كما تعمل مع مقابض الملفات ، لإغلاق المقبس عن أعمالة فلانستطيع القراءة منه أو الكتابة الية ، تعيد الدالة القيمة true في النجاح و في الخطأ تعيد undef وتترك رسائلة الخطأ في المتغير $! .

وتأثير الدالة close() على الطرف الاخر للإتصال هو مشابة لما يحدث في الأنبوب ، فبعد إغلاق المقبس في طرف وإي قراءة من المقبس على الطرف الاخر تعاد end of file (EOF) ،وإي كتابة يودي إلى إستثناء PIPE .

$boolean = shutdown (SOCK, $how)

shutdown() هي نسخة أدق من close() التي تمنح السيطرة على أطفاء جزء من الإتصال ذو الاتجايين ، ولدي الدالة وسيطين فاول وسيط هو المقبس المتصل ويشير الثاني $how الى كيفية إطفاء الاتجاة بوضع أحد القيم الصحيحة المشاهد في الجدول أسفل ، بوضع القيمة 0 تغلق المقبس لمنع قراءة اكثر من ذلك ، والقيمة 1 تغلق المقبس للكتابة ، والقيمة 2 تغلق المقبس للقراءة والكتابة كليهما (كماهي close() )



القيمة الوصف
0 إغلاق القرءة للمقبس
1 إغلاق الكتابة للمقبس
2 إغلاق المقبس بإكمال


بالأضافة إلى مقدرة shutdown() لإغلاق نصف half-close المقبس لها فائدة اخرى على close() ، فأن أستدعينا fork() في اي مكان في الكود ينسخ القيم الحالية ومقابض المقبس في عمليات الاطفال ، وكما هو معهود من close() على إي نسخة في الاطفال لاتغلق المقبس أو الإتصال حتى تغلق جميع النسخ الاخر مقبس الإتصال أيضاً (مقابض الملفات لها نفس السلوك ) ، نتيجة لذلك العميل على الطرف الاخر من الإتصال لايستلم EOF حتى يغلق كلاً من الإب و أطفاله نسخهم ، على النقيض من ذلك الدالة shutdown() التي تغلق جميع النسخ للمقبس وترسل EOF مباشرتاً ، هذة النقطة سنستغلها في العديد من الإوقات أثناء هذا الكتاب .



A TCP Echo Server

بالإضافة الى مثال السابق TCP client نشاهد مثال اخر TCP server بسيط في كتابة البرامج ويتذكر بأن خطوات انشاء الخادم هي :




عندما accept() تستلم الاتصال القادم ،وتعيد مقبس جديد متصل إلى العميل




في المثال الخادم نسخة ملفوفة بعض الشيء من echo server القياسي الفعلي في النظام ، يطبع البرنامج أي مرسل الية ولكن بدلاً من أعادة أرسالة حرفياً يقوم بعكس سطر النصوص بواسطة reverse() لكن تبقي السطر على النهاية ، فاي واحد يرسل "Hello world!," يعود السطر مطبوع "!dlrow olleH." وليس هناك سبب لهذا العمل سواء عكس النصوص فقط .

يستعمل الخادم العميل السابق tcp_echo_cli1.pl أو مع برنامج التلنت القياسي

#!/usr/bin/perl
# file: tcp_echo_serv1.pl
# A TCP Echo Server

# usage: tcp_echo_serv1.pl [port]

use strict;
use Socket;
use IO::Handle;
use constant MY_ECHO_PORT => 2000;

my ($bytes_out,$bytes_in) = (0,0);

my $port     = shift || MY_ECHO_PORT;
my $protocol = getprotobyname('tcp');

$SIG{'INT'} = sub { 
    print STDERR "bytes_sent = $bytes_out, bytes_received = $bytes_in\n";
    exit 0;
};

socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die "socket() failed: $!";
setsockopt(SOCK,SOL_SOCKET,SO_REUSEADDR,1)    or die "Can't set SO_REUSADDR: $!" ;

my $my_addr = sockaddr_in($port,INADDR_ANY);
bind(SOCK,$my_addr)    or die "bind() failed: $!";
listen(SOCK,SOMAXCONN) or die "listen() failed: $!";

warn "waiting for incoming connections on port $port...\n";

while (1) {
  next unless my $remote_addr = accept(SESSION,SOCK);
  my ($port,$hisaddr) = sockaddr_in($remote_addr);
  warn "Connection from [",inet_ntoa($hisaddr),",$port]\n";

  SESSION->autoflush(1);
  while (<SESSION>) {
    $bytes_in  += length($_);       
    chomp;
    my $msg_out = (scalar reverse $_) . "\n";
    print SESSION $msg_out;
    $bytes_out += length($msg_out);
  }
  warn "Connection from [",inet_ntoa($hisaddr),",$port] finished\n";
  close SESSION;

}

close SOCK;

الشرح :

شحن الوحـدات وتهيئة الثوابت والمتغيرات كما في السابق ، وتخصيص منفذ خاص 2000 لعدم حدوث تضارب بأي خادم echo server موجود آخر ، ثم تنزيل قائد الاعتراض INT لبرنامج الخادم في حالة أرسال إشارة بضغط المستخدم ^C يطبع هذا القائد البايتات المتراكمة والخروج .

لانشاء مقبس نستخدم socket() ، ثم وضع خيار المقبس SO_REUSEADDR بإستدعى setsockopt() ،يستخدم هذا الخيار ليسمح لنا في قتل وإعادة تشغيل الخادم مباشرتاً ، تحت أي نظام في حال عدم تحديد هذا الخيار لان يسمح لنا بأعادة ربط rebind عنوان المحلى حتي تأخذ الاتصالات القديمة الخروج timed out بمعنى لانتمكن انشاء مقبس خادم تحت نفس المنفذ حتى يتم قتل البرنامج واذا نفذنا نفس البرنامج السابق مرة اخرى يظهر اعتراض address already in use يعني ان منفذ البرنامج محجوز اي لااستطيع ان استخدام نفس البرنامج اكثر من مرة وبإستخدام هذا الخيار يجعل من الممكن اعادة استخدام البورت بأكثر من مرة

ثم ربط المقبس الى العنوان المحلي لتخصيص العنوان المحلي الى المقبس ، وهذا العنوان منشأ مسبقاً بإستخدام sockaddr_in() التي تمرر رقم المنفذ و اي عنوان INADDR_ANY كعنوان IP ، وتمثل INADDR_ANY كالwildcard وهذا الوسيط يسمح لنظام التشغيل لقبول الاتصالات على اي عناوين IP للمضيف ( من ضمنها عنوان القلبloopback address و اي عناوين الشبكه network interface card )

لجعل المقبس جاهز لقبول الاتصالات القادمة نستخدم listen() ، وفي التكرار اللانهائية ينتظر لخدمة الإتصالات القادمة ، في بداية التكرار تستدعى الدالة accept() التي تستعمل المقبس المستمع كوسيط ثاني ، وأسم المقبس الجديد المسمى (SESSION) في أول وسيط ( نعم فهذا الوسيط غريب الى حد ما) فأن نجح الاستدعى تعيد العنوان المضغوط للمقبس البعيد كناتج لها وتعيد المقبس المتصل في SESSION .

وبفك ضغط عنوان العميل المعاد في الدالة accept() نستخدم الدالة sockaddr_in() في سياق القائمة لنحصل على منفذ وعنوان IP العميل ، ومن ثم طباعة العنوان إلى الخطأ القياسي ، نكتب هذة المعلومات في التطبيقات الحقيقية الى ملف اللوج time-stamped log .

بقية محتوى التكرار الرئيسي لمعالجة إتصالات العميل عن طريق المقبس المتصل المعاد ، اولاً تحديد المقبس المتصل SESSION الى نمط autoflush لمنع مشاكل التخزين المؤقت ثم التكرار الفرعي لقراءة أو استقبل سطر في كل مرة بإستخدام <> ثم عكس نص السطر وأرسال النص مرة اخرى الى العميل عن طريق الكتابة الى مقبض المقبس SESSION ، يستمر التكرار حتى يعيد <SESSION> القيمة undef التي تشير الى ان الند إغلق طرف الاتصال هكذا سنغلق المقبس SESSION وطباعة رسالة إنتهاء الاتصال ونعود مرة اخرى الى الدالة accept() للإنتظار أتصال قادم آخر واذ لم نكتب الحلقة الرئسية لايتمكن الخادم الا في استقبال عميل واحد والخروج من البرنامج لكن اذا اردنا استقبال اكثر من عميل في نفس الوقت نستخدم تعدد الخيوط والعمليات كما سنراه لاحقاً .

بعد الانتهاء من التكرار اللانهائي عند الخروج بواسطة مفتاح الأعتراض لكي ينهى ، عمل التنظيفات اللأزمة بإغلاق المقبس المستمع .


بتشغيل الخادم من نأفذة الاوامر يطبع الرسالة "waiting for incoming connections" ينتظر الى ان يستلم أتصال قادم وفي الجلسة المشاهدة هنا في الخرج إتصال عميلين على عنوان 127.0.0.1 والاخر على العنوان 192.168.3.2 وبإرسال إشارة الأعتراض للخادم يطبع الاعداد المرسلة والمستلمة من قائد الإشارة INT .


Output

% tcp_echo_serv1.p1
waiting for incoming connections on port 2000...
Connection from [127.0.0.1,2865]
Connection from [127.0.0.1,2865] finished
Connection from [192.168.3.2,2901]
Connection from [192.168.3.2,2901] finished
bytes_sent = 26, bytes_received = 26



قائد الإشارة INT في هذا الخادم ينتهك توصية ماتم مناقش سابقاً في قسم الإشارات ولان يعملان تلك القائدة بإي I/O ، بإلاضافة بإستدعى exit() بضمن القائد فقد يحدث أستثناء قاتل fatal تودي في أرصفة الونيدوز بإيقاف تشغيلهم ، وسنرى اكثر من ذلك بطريقة اكثر رشاقة لإيقاف تشغيل الخادم shutting down a server في الفصل العاشر Forking Servers and the inetd Daemon .

دوال المقبس المتعلقة بالإتصالات القادمة :

هناك ثلاث دوال رائيسية يحتاجها الخوادم لمعالجة الإتصالات القادمة .


$boolean = bind (SOCK,$my_addr)

ربط العنوان المحلي الى المقبس ، تعيد القيمة true في النجاح وغير ذلك تعيد false ، وتستدعى هذة الدالة بعد إنشاء المقبس مع socket() وايضاً يكون العنوان المضغوط $my_addr منشأ مسبقاً بواسطة sockaddr_in() ، فقد يأتي جزء المنفذ من العنوان اي منفذ غير مستخدم على النظام ، وقد يأتي جزء عنوان IP لعنوان أحد وصلات الشبكة للمضيف أو loopback address أو wildcard INADDR_ANY .

يتطلب منك على أنظمة اليونكس أمتيزات الروت لعمل bind للربط إلى المنأفذ المحجوزة ماتحت 1024 ، وعند محاول الربط في غير امتيزات الروت تعيد الدالة undef ويوضع المتغير $! الى الخطأ EACCES لعدم تمكن الصلاحية ("Permission denied").

عادتاً تستدعى الدالة bind() في الخوادم لكي يربط المقبس المنشأ حديثاً مع المنفذ المعروف للخدمات النظام ، وعلى اي حال يمكن ان يستدعى العميل bind() ايضاً اذا رغب في تحديد منفذ محلي أو/و وصلة الشبكة .


$boolean = listen (SOCK,$max_queue)

تخبر هذة الدالة نظام التشغيل بأن ذلك المقبس سوف يستخدم لقبول الإتصالات القادمة ، ولدى الدالة وسيطين هم مقبض المقبس الذي قد أنشاء من قبل مع socket() ، والقيمة الصحيحة integer التي تحدد عدد الإتصالات القادمة التي توضع على الطابور ، ويحدد النظام حجم أقصى الطابور بأعلى عدد تراكمي معلقة على وشيك الاتصال وعند تحديد قيمة أكبر من هذا الحجم الاقصى تعمل الدالة listen() في قطع الزيادة ووضع الحجم الاقصى التي يسمح بها النظام ، تصدر الوحـدة Socket الثابت SOMAXCONN لإيجاد هذة القيمة الاقصى .

في النجاح تعيد الدالة القيمة true ويجعل المقبس SOCK يستمع ، وغير ذلك فهي تعيد undef ويوضع المتغير $! للخطأ الملائم .


$remote_addr = accept (CONNECTED_SOCKET,LISTEN_SOCKET)

عندما المقبس يجعل في الإستماع ، وفي حالة وجود اتصالات قادمة توضع على الطابور ومن الضرورة لقبول هذة الإتصالات القادمة نستدعى accept() ، لدى الدالة وسيطين وهم أسم المقبس المتصل CONNECTED_SOCKET أسم المقبص ليستلم المقبس المتصل حديثاً ، وثاني وسيط لإسم المقبس المستمع LISTEN_SOCKET ، في النجاح تعيد عنوان المضيف البعيد مضغوط ويصبح المقبس CONNECTED_SOCKET مرتبط مع الأتصال القادم .

يمكن في الكود فيما يلي هذة الدالة أستخدام المقبس المتصل CONNECTED_SOCKET لتواصل مع الند ، وبالعكس لان تحتاج لكى نقوم بأنشاء المقبس CONNECTED_SOCKET مرة اخرى واذا مازلت مرتبك في صياغة الدالة توقع بان accept() هي دالة خاصة من الدالة open() مع أستبدال LISTEN_SOCKET بأسم الملف.

في حالة لايوجد أتصال قادم منتظرة لكي تقبل تبقى الدالة في block حتى يصل اتصال ، وان إتصل أكثر من عميل وكان وصول العملاء أسرع من برنامجك بإستدعى accept() فسوف يوقفون في الأنتظار في الطابور المحدد من قبل الدالة listen() .

تعيد الدالة accept() غير معرف أذا حدث أي رقم من شروط الخطأ error conditions ويوضع المتغير $! برسائلة الخطأ الملائمة .


$my_addr = getsockname (SOCK) $remote_addr = getpeername (SOCK)

عند الرغبة في إستعادة العنوان المحلي أو البعيد المرتبط مع المقبس ،ولهذا الشيء نستخدم getsockname() أو getpeername()

تعيد الدالة getsockname() العنوان الثنائي مضغوط من المقبس للطرف المحلي ، أو غير معرف اذا المقبس غير محدد unbound ، وبطريقة نفسها تتصرف الدالة getpeername() لكن تعيد العنوان من المقبس للطرف البعيد أو غير معرف اذا المقبس غير متصل .

في حالة أما العنوان المعاد يجب يكون مفكوك الضغط مع sockaddr_in() وللتوضيح في هذا المثال :

if ($remote_addr = getpeername(SOCK)) { my ($port,$ip) = sockaddr_in($remote_addr); my $host = gethostbyaddr($ip,AF_INET); print "Socket is connected to $host at port $port\n"; }




Limitations of tcp_echo_serv1.pl

بالرغم من ان tcp_echo_serv1.pl مكتوب ويعمل بشكل صحيح الا ان هناك بعض العوائق التي سوف نواجهها في فصول اخرى ومن ضمن هذة العوائق :

  1. لايدعم الإتصالات القادمة المتعددة : وتعتبر مشكلة في ظل ان الخادم يقبل إتصال قادم واحد فقط في نفس الوقت ، في أثناء هذة الفترة يكون مشغول في خدمة الإتصال الحالي وباقي الطلبات تصطف في الطابور حتي ينتهي من معالجة الإتصال الحالي والعودة مرة اخرى الى أستدعى الدالة accept() في التكرار الرئيسي ،واذا كان عدد العملاء في الطابور تجاوز القيمة المحددة في listen() فسيرفض اي أتصال قادمة جديد.

    لتفادي هذة المشكلة يجب إضافة لهذا الخادم إنجازات إضافية كتأدية بعض concurrent processing مع الخيوط أو العمليات أو بأكثر من ذلك مع تعدد الارسال multiplex لوظائف الخرج والدخل ،وسوف نناقش هذة التقنيات لاحقاً .

  2. يبقي الخادم في الأمامية foreground : فبعد تشغيل الخادم يبقى في الأمامية حيث اي إشارة من لوحة المفاتيح ( مثل ^C ) تستطيع لإعتراض وظائفة ، مع الخوادم Long-running servers ستريد لعدم ترابطهم من لوحة المفاتيح وتضعهم في الخلفية background ، وهذة التقنيات مناقشة في فصل Forking Servers and the inetd Daemon

  3. تسجيل اللوج في الخادم بدائية : يسجل الخادم معلومات حالة اللوج الى نأتج سيل الخطأ القياسي ، على اي حال الخادم القوي البنية يشغل كعملية في الخلفية و لايحب ان يكون لدية أي خطأ قياسي مكتوب إلية ، ويجب ان يذيل append الخادم مدخلات اللوج الى ملف أو أستخدام وسائل logging الخاصة بنظام التشغيل نفسة ، وهذا مايناقش في الفصل Bulletproofing Servers .






ضبط خيارات المقبس (Adjusting Socket Options )

لدى المقابس العديد من الخيارات التي تسيطر على وضعيات مختلفة للوظائفهم ، ومن بين هذة الأشياء نستطيع ضبط جحم الbuffers المستخدمة في إرسال واستلام البيانات ، وتحديد قيم أزمنة الخروج timeouts للمرسلة و المستلمة ، وسوءاً استخدم المقبس لإستلام تراسلات البثbroadcast transmissions .

تؤدي الخيارات الأفتراضية لأكثر الأغراض لكن من حين لآخر قد ترغب في ضبط بعض الخيارات لتحسين التطبيق أو لتمكين ميزات أختيارية لبروتوكول TCP/IP ، واغلب هذة الاستخدامات مع الخيار SO_REUSEADDR الذي موجود في تطبيقات الخوادم كثيراً .

تفحص أو تغير خيارات المقبس مع الدوال getsockopt() و setsockopt() .


$value = getsockopt (SOCK,$level,$option_name); $boolean = setsockopt (SOCK,$level,$option_name,$option_value);

تسمح الدالة getsockopt() لفحص خيارات المقبس و setsockopt() لتغير خيارات المقبس ، فأول وسيط هو مقبض المقبس المنشأ سابقاً ، ويشير الوسيط الثاني $level الى المستوى مكدس الشبكة التي نرغب بالعمل علية ، وغالباً يشغل مع الثابت SO_SOCKET الذي يشير للوظائف على المقبس نفسة ، على اي حال getsockopt() , setsockopt() تستعمل من حين لآخر لضبط خيارات في بروتوكلات TCP او UDP ، في هذة الحالة يستخدم رقم البروتوكول المعاد من قبل الدالة getprotobyname() .

الوسيط الثالث $option_name قيمة عددية صحيحة محددة من قائمة كبيرة من الثوابت .واخر وسيط $option_value هو القيمة لوضع الخيار إلية ، او تعيد undef اذا لم يكن ملائم ، وفي النجاح تعيد getsockopt() القيمة من الخيار المطلوب أو غير معرف undef أن فشل الإستدعى ، وفي نجاح تعيد setsockopt() القيمة true في حالة قد وضع الخيار بنجاح ، وغير ذلك فسوف تعيد undef .

قيمة الخيار $option_value لها قيمة Boolean اي توضع في بعض الاحيان للإشارة سواءاً الخيار يجب ان يفتح او يطفئ turned on و off ، في حالتة الكود غير خاص سنتحتاج للوضع او لفحص القيمة . فمثلاً هنا كيفية وضع القيمة SO_BROADCAST الى ال true، البث broadcasting سنقاشها لاحقاً

setsockopt(SOCK,SO_SOCKET,SO_BROADCAST,1);

وهنا لكيفية أسترجاع القيمة الحالية للعلم :

my $reuse = getsockopt(SOCK,SO_SOCKET,SO_BROADCAST);

على اي حال فبعض الخيارات تمثل بناءاً على الاعداد الصحيحة integers أو على أنواع البيانات الاكثر باطنية esoteric مثل تركيبات C timeval structures ، وبطبيعة الحال يجب ضغط القيم الى الشكل الثنائي قبل عبورهم الى الدالة setsockopt() ونفك ضغطهم بعد إستدعئ الدالة getsockopt() ، هنا طريقة لتوضيح لإستعادة الحجم الاقصى للذاكرة buffer أو maximum size of the buffer التي يستخدمها المقبس لتخزين البيانات الخارجة ، وبتصرف الخيار SO_SNDBUF على العدد الصحيح المضغوط ( مع الصيغة "I" ):

$send_buffer_size = unpack("I",getsockopt($sock,SOL_SOCKET,SO_SNDBUF));


خيارات المقبس المشتركة

في الجدول اسفل قائمة الخيارات المشتركة للمقبس المستخدمة في برمجة الشبكات وتستورد هذة الثوابت بالإفتراضي عند شحن وحـدة Socket .


SO_REUSEADDR يسمح لمقبس TCP لإعادة ربط rebind إلى العنوان المحلي الذي في قيد الإستعمال ، ويأخذ هذا الخيار وسيط Boolean يشير الى سواء كان العنوان يفعل reuse أو لا .


SO_KEEPALIVE عندما يوضع إلى القيمة true يخبر المقبس المتصل لإرسال الرسائل إلى الند بشكل متقطع ،وفي حالة فشل المضيف البعيد الرد للرسائل فسوف تستلم عمليك إشارة PIPE وفي المرة التالي تحاول للكتابة الى المقبس ، وفترة رسائل keepalive متفاوتة من نظام الى نظام آخر ( وهي 45 ثانية على نظامي الينكس)


SO_LINGER يحدد هذا الخيار فترة زمنية من الوقت يبقي المقبس مفتوح بعد ان يغلق close() ، لكي نسيطر على مالذي سوف يحدث عندما نحاول إغلاق مقبس TCP ومازلت فيه بيانات غير مرسلة ومازالت مطوبره حتى الان، فهذا الخيار ليس مهم بطبع لان نظام التشغيل يستمر في أرسال البيانات الغير مرسلة على الخلفية بعد ان تسرجع close() مباشرتاً القيمة العائدة ، في حالتة انك تفضل استخدام الخيار SO_LINGER لوضع من أستدعى close() في block حتى يتم أرسال جميع البيانات الذي يسمح لك بفحص القيمة العائدة من close() لإكتمال النجاح .

وعلى خلاف خيارات المقبس الأخرى يشتغل الخيار SO_LINGER على نوع البيانات المضغوطة المعروفة بتركيب الاطالة linger structure ، وهذا التركيب يشمل عددين صحيحين وهم : العلم flag الذي يشير سواءاً أكان SO_LINGER مفعل أو لا ، وقيمة زمن الخروج timeout التي تشير الى الرقم الأعلى في الثواني التي يجب على close() الاطالة قبل الإعادة ، ولابد من ان يكون linger structure مضغوط ومفكوكة الضغط بإستخدام صيغة II :

$linger = pack("II",$flag,$timeout);

فمثلاً لجعل أطالة المقبس والإستقاض في الثانية 120 نستدعى التالي :

setsockopt(SOCK,SOL_SOCKET, SO_LINGER,pack("II",1,120))
                            or die "Can't set SO_LINGER: $!";

SO_BROADCAST الخيار صحيح فقط للمقابس UDP ، وفي حالة وضع إلية القيمة true يسمح للدالة send() لتكون الرزم مرسلة إلى عنوان البث broadcast address لكي يستلمها جميع المضيفين على الشبكة الفرعية المحلية .


العلم SO_OOBINLINE يسيطر على كيفية المعلومات out-of-band أو المستعجلة معالجتها ، وهذة الميزة تمنح لإنذار الند بحضور البيانات ذات الأعلى الأولوية ، ستناقش كيفية عمل هذا في فصل TCP Urgent Data


SO_TYPE هو خيار للقراءة فقط ، الذي يعيد نوع المقبس (مثل SOCK_STREAM ) ويتطلب فك الضغط unpack هذة القيمة مع الصيغة I قبل ان يستعمل الخيار ، طريقة IO::Socket المسمى sockopt() تعمل التحويل بشكل آلياً ستناقش لاحقاً .

الخيار الوصف
SO_REUSEADDR تمكين من أعادة أستخدام العنوان المحلي
SO_KEEPALIVE تمكين التراسل للرسائل "keep-alive" في فترات .
SO_LINGER أطلاه على الإغلاق اذا كانت البيانات مازلت مقدمة
SO_BROADCAST منح المقبس لإرسال الرسائل الى عنوان البث broadcast address
SO_OOBINLINE أحتفظ بالبيانات out-of-band في شكل inline.
SO_SNDLOWAT الحصول أو وضع الحجم لoutput buffer "معينة في low water "
SO_RECVLOWAT الحصول أو وضع الحجم لinput buffer "معينة في low water "
SO_TYPE الحصول على نوع المقبس ( قراءة فقط).
SO_ERROR الحصول والتنظيف للخطأ الأخير على المقبس ( قراءة فقط ).



وأخيراً للقراءة فقط الخيار SO_ERROR الذي يعيد كود الخطأ في حالتة كان موجود وعند وجود اكثر من خطأ تعيد كود الخطأ للوظيفة الأخيرة على المقبس ، ويستعمل هذا الخيار لوظائف الغير متوافقة زمنياً asynchronous مثل الاتصالات الغير متكتلة nonblocking connects في فصل Nonblocking I/O وبعد القراءة ينظف الخطأ ، وكما في السابق مستخدمين الدالة getsockopt() يتطلب الى فك الضغط مع الصيغة I قبل إستخدام الخيار ولكن هذا يأخذ بأهتمام بشكل آلي من قبل IO::Socket .



خيار المقبس SO_REUSEADDR

ينشط العديد من المطورين العلم SO_REUSEADDR في تطبيقات الخادم الذي يمنح الخادم لإعادة الربط rebind إلى العنوان الذي في قيد الإستعمال ، ويمنح الخادم الى إعادة تشغيل restart مباشرباً بعد التحطم او بعد قتله ، وبدون هذا الخيار تفشل الدالة bind() حتى تأخذ الاتصالات القديمة timed out وربما قد تأخذ تلك العملية عددة دقائق . التعبير في إدخال السطر التالي من الكود بعد أستدعى socket() وقبل إستدعى bind() :

setsockopt(SOCK,SOL_SOCKET,SO_REUSEADDR,1) o r die "setsockopt: $!";

الجانب السلبي من وضع الخيار SO_REUSEADDR هو السماح لك لإتشغيل الخادم مرتين ، فكلتا العمليات ستكون قادة للربط إلى نفس العنوان بدون تسبب بالخطأ ، وهم من ثم سيتنافسون للإتصالات القادمة وهذا مايودي الى تشويش الناتج ، والخوادم المطورة لاحقاً في الفصول القادم ( Forking Servers and the inetd Daemon و فصل Bulletproofing Servers وفصل IO::Poll ) تتفادي هذة الإمكانية بإنشاء ملف عندما يبدأ starts up البرنامج ويحذفه على الخروج exit ، ويمنع من تشغيل الخادم في حالتة وجد الخادم بأن هذا الملف موجود فيه .

لايسمح نظام التشغيل لعنوان المقبس أن يحدد bound من قبل عملية مستخدم لتكون محددة من قبل عملية مستخدم آخر بغض النظر من وضع الخيار SO_REUSEADDR .



الدوال fcntl() و ioctl()

بالأضافة الى خيارات المقبس فهناك عدد من attributes لضبط بإستخدام الدوال fcntl() و ioctl() ، تناقش الدالة fcntl() في الفصل Nonblocking I/O ، حيث تستعمل لفتح Nonblocking I/O ومرة أخر نناقشها في الفصل TCP Urgent Data للوضع المقبس المالك لكي نستلم إشارة URG عند إستلام البيانات المستعجلة TCP urgent data .

بينما سوف نستخدم الدالة ioctl() ايضاً في الفصل TCP Urgent Data ، حيث نستخدمها لتضمين الدالة sockatmark() لمعالجة البيانات المستعجلة ، ومرة اخر نناقشها في الفصل Multicasting حيث ننشأ تشكيلة من الدوال لفحص وتعديل عناوين IP المخصصة لوصلات الشبكة .



دوال اخرى متعلقة بالمقبس

بالأضافة الى الدوال التي رأينا فهناك ثلاث دوال داخلية للبيرل تتعلق بالمقابس وهم send(), recv(), socketpair() ، سوف نستعمل هذة الدوال في الفصول من هذا الكتاب لاحقاً كفصل TCP urgent data وفصل UDP protocol .


$bytes = send (SOCK,$data,$flags[,$destination])

تستخدم الدالة send() المقبس المشار الية في أول وسيط لكي يسلم محتوى البيانات $data إلى عنوان المستقبل $destination ، في حالتة تم إرسال البيانات المطوبرة بنجاح تعيد send() عدد البايتات التي إرسلت ، وغير ذلك فهي تعيد undef ، أما الوسيط الثالث $flags هو bitwise OR للصفر أو لأكثر من خيارين في القائمة المدرجة في الجدول أسفل .

يناقش العلم MSG_OOB في التفصيل في فصل TCP Urgent Data ، اما العلم MSG_DONTROUTE يستخدم في برامج التشخيص والتوجيه routing and diagnostic programs ولان نناقشها في هذا الكتاب ، وعموماً يجب امرار الصفر كقيمة للأعلام $flags لكي يقبل السلوك الإفتراضي .

في حالتة كان المقبس هو لمقبس TCP متصل فيجب ان لا يكون $destination محدد وتصبح عمل الدالة send() مساوية تقريباً للدالة syswrite() ، مع UDP sockets فالمستقبل يمكن ان يكون مغير مع كل أستدعى للدالة send() .


$address = recv (SOCK,$buffer,$length,$flags)

الدالة recv() تقبل للعدد البايت بالطول $length المحدد التي تستلم من المقبس المشار ووضعهم الى متغير $buffer ‏، ويزتاد أو ينخفض هذا المتغير بحسب طول القراءة الفعلية ، بينما وسيط الاعلام $flags له نفس الأهمية كوسيط مطابق لما جاء في $send ويجب عادتاً ان يوضع الى الصفر .

تعيد recv() في النجاح عنوان المقبس مضغوط لمرسل الرسالة ، وفي حال الخطأ تعيد الدالة undef ويوضع $! بشكل ملائم . وعندما تستدعى الدالة على مقبس TCP متصل بذلك recv() تتصرف كما تعمل الدالة sysread() ماعدا في إرجاع عنوان الند ، والفائدة الحقيقية للدالة recv() هي لإستلام datagrams لتراسلات UDP transmissions .

$boolean = socketpair (SOCK_A,SOCK_B,$domain,$type,$protocol)

تنشأ الدالة مقبسين متصلة من النهاية إلى النهاية ولديها نفس المفاهيم في دالة socket() ، في النجاح تعيد true وتفتح المقابس على SOCK_A و SOCK_B .




الخيار الوصف
MSG_OOB البايت المرسلة لبيانات المستعجلة على مقبس TCP
MSG_DONTROUTE تجاوز جـداول التوجيه


الدالة socketpair() مماثلة للدالة pipe() لما تم ذكرة سابقاً في الفصل 16 ماعدا انها ذو أتجاهين bidirectional ، ومع أنشاء زوج من المقابس في السكربت ومن ثم إستدعى fork() وإستعمال مقبس في الأب والاخر في الطفل لتكوين إتصالات ذو الإتجاهين بين الطفل والاب كمافي أسكربت socketpair.pl ، بينما تستخدم الدالة socketpair() مبدئياً للبروتوكول INET الأ أن أغلب الانظمة تدعم هذة الدالة فقط لإنشاء مقابس UNIX-domain في صيغة :

socketpair(SOCK1,SOCK2,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die $!;

سنرى لاحقاً أمثلة أكثر بإستخدام مقابس UNIX-domain في الفصل UNIX-Domain Sockets



الشروط الإستثنائية أثناء إتصالات TCP

بروتوكول TCP قوي البنية تجاه شروط الشبكة الرديئة ويستطيع النجاة من الإتصالات البطيئة ، والروترات المقشرة flaky routers ، وعمليات إنقطاع(outages اي كميات مفقودة )الشبكة المتقطعة ومع مختلف الأعداءات السيئة misconfigurations ، ومازالت الرسالة لتسليم متوافق ، وسيل البيانات محررة من الخطأ error-free .

يتغلب TCP على جميع المشاكل ، وفي هذا القسم سناقش الإستثناءات المشتركة بالإختصار ، وايضاً سناقش بعض الأخطأ البرمجية المشتركة .


الإستثناءات أثناء connect()

الأخطاء المختلفة هي مشتركة أو موحدة أثناء الإستدعى إلى الأتصال connect() .

  1. المضيف البعيد حاظر لكن ليس هناك خادم يستمع بينما يحاول العميل الإتصال :
    يحاول العميل للإتصال الى المضيف البعيد ولكن ليس هناك خادم يستمع إلى المنفذ المشار، لذلك تجهض الدالة connect() مع خطأ رفض الإتصال (ECONNREFUSED) أو "Connection refused" .

  2. المضيف البعيد غير حاضر بينما يحاول العميل الأتصال :
    يحاول العميل للأتصال الى المضيف البعيد ولكن لايشتغل المضيف ( أما بسبب قد تحطم أو من غير الممكن الوصول الية crashed or unreachable ) ، في هذة الحالة تكتل blocks الدالة connect() حتى تخرج times-out مع الخطأ (ETIMEDOUT) أو "Connection timed out" ، يسمح TCP للإتصالات connections الشبكة للعملاء البطيئة ولذلك timeout قد لايحدث لعدة دقائق .

  3. الشبكة غير معدة بشكل صحيح misconfigured :
    يحاول العميل للإتصال الى المضيف البعيد ولكن لايستطيع نظام التشغيل فهم كيفية توجيهه route إلى وجهته المطلوبة ، بسبب الإعداءات المحلية سيئة misconfiguration وغير معدة بشكل صحيح أو لربما يفشل الروتر في مكان ما طوال الطريق ، في هذة الحالة تفشل الدالة connect() مع الخطأ (ENETUNREACH) أو "Network is unreachable" .

  4. هناك خطأ برمجي :
    مختلف الاخطاء البرمجية المشتركة (الشائعة )، فمثلاً في محاولة لإستدعاء connect() مع مبقض الملف بدلاً من المقبس ينتج إلى الخطأ (ENOTSOCK) أو "Socket operation on non-socket" ، وبمحاول أستدعاء connect() على المقبس قد أتصل ينتج إلى الخطأ "Transport endpoint is already connected" (EISCONN) .



الخطأ ENOTSOCK يمكن ان يعاد أيضاً من قبل إستدعاءات المقبس الاخرى مثل أستدعى bind(), listen(), accept(), and the sockopt() .



الإستثناءات أثناء الكتابة والقراءة :

عندما يكون قد ثبت الإتصال فما زالت الاخطاء محتملة وتقريباً قد تصادف بعض هذة الاخطاء اثناء عملك مع برامج الشبكة .

  1. يتحطم برنامج الخادم بينما العميل متربط إلية :
    فأن تحطم برنامج الخادم أثناء جلسة الإتصال ، سيغلق نظام التشغيل المقبس ، ومن منظور العميل يعتقد بالحالة مماثلة إلى برنامج البعيد بتعنده على أغلاق نهايته .

    على القراءة يؤدي إلى EOF في المرة التالية من إستدعى read() or sysread() ، وعلى الكتابة يؤدي هذة إلى إستثناء PIPE كما بالضبط مع مثال الأنبوب سابقاً ، وفي حالتة إعترض العميل ومن ثم معالجة PIPE تعيد الدالة print() or syswrite() القيمة false ويوضع المتغير $! إلى "Broken pipe" (EPIPE) ، وغير ذلك ينتهي العميل بإشارة PIPE .

  2. يتحطم المضيف الخادم بينما الإتصال مثبت :
    على الناحية الأخرى ، في حالتة تحطم المضيف بينما إتصال TCP مفعل ، فليس لنظام التشغيل فرصة لإنهاية الإتصال بشكل رشيق ، وعلى العميل لاتوجد طريقة لنظام التشغيل لتميز بين المضيف الميت أو يواجه إنقاع شبكة طويلة جداً ، وسيتمر مضيف العميل من إعادة إرسال IP packets في hopes التي تظهر مرة اخرى على المضيف البعيد ، ومن منظورك إستدعاء القراءة أو الكتابة الحالية ستكون متكتلة بشكل غير محدود .

    وفي بعض الأوقات لاحقاً عندما المضيف البعيد يعاود السطر فسوف يستقبل واحد من الرزم المعاد إرسالها في المضيف المحلي (العميل) ،فلا يعرف ما العمل معه فيرسل المضيف مرة اخرى رسالة منخفضة المستوى تخبر فيها المضيف المحلي بأن الأتصال مرفوض rejected ، وعلى هذة النقط الاتصال هو broken ويستلم برنامج العميل أما EOF أو خطأ الأنبوب ويعتمد ذلك على المحتوى operation .

    الطريقة الاولي لتجنب التكتل الغير محدود هي بوضع الخيار SO_KEEPALIVE على المقبس ، في هذة الحالة حالة خروج times out الأتصال بعد بضع فترة unresponsiveness أو هاملة فيما المقبس مغلق ، زمن الخروج timeout للخيار keepalive طويل نسبياً (في بعض الحالات في الدقائق ) ولايمكن أن يغير .

  3. تهبط الشبكة بينما الإتصال مثبت :
    اذا الروتر أو قطع الشبكة تهبط goes down بينما الإتصال مثبت ،يجعل المضيف البعيد unreachable ، وتكتل I/O operation الحالية حتى يعاد من تخزين الإتصال connectivity ، وفي هذة الحالة على أي حال عندما الشبكة تعاد تخزين الإتصال فعادتاً يستمر كما لم يحدث شيء ، وستكمل I/O operation بنجاح .


هناك العديد من الإستثناءات الى هذة ، وعلى أي حال في حالتة بدلاً من هبوط going down بسيط ، أو روتر من الروترات يظهر على طول الطريق رسالة خطأ مثل "host unreachable," فأن ذلك ينهي الإتصال والتأثير مماثل للسيناريو (1) .الحالة المشتركة الاخرى هي بأن الخادم البعيد له نظام timeout خاص به حيث هي يخرج times out ويغلق الإتصال حالما network connectivity تعيد التخزين restored .